Skip to content

internal/as_user: drop supplementary groups - #2301

Open
JasonColapietro wants to merge 2 commits into
coreos:mainfrom
JasonColapietro:as-user/drop-supplementary-groups
Open

internal/as_user: drop supplementary groups#2301
JasonColapietro wants to merge 2 commits into
coreos:mainfrom
JasonColapietro:as-user/drop-supplementary-groups

Conversation

@JasonColapietro

Copy link
Copy Markdown

Summary

set_eids() in internal/as_user/as_user.c switches the effective gid and uid
to the target user, but never touches the supplementary group list. The
privilege-dropped thread therefore keeps the caller's group memberships — in
practice root's — while acting as that user.

This drops the list with setgroups(0, NULL) before the gid and uid are
relinquished, while the thread still holds the privilege required to make that
call, following the revocation order described in
CERT POS36-C.

Reproducer

I built a probe against as_user.c before and after the change. It makes the
caller (root) a member of gid 4242, creates a directory owned root:4242 with
mode 0770 — so "other" has no access — and then asks au_open() to create a
file inside it as uid=65534 gid=65534, which is not a member of 4242:

result
before au_open succeeded (fd=3): the thread kept root's membership of gid 4242
after au_open failed with EACCES, as it should

So the retained groups do grant real access during the switch, not just a
theoretical capability.

Impact

Low, and I don't want to oversell it. As #2242 notes, Ignition already runs as
root and as_user is defense-in-depth rather than a security boundary; the only
caller is writeAuthKeysFile() in internal/exec/util/passwd.go, writing SSH
authorized keys during first boot, where root's supplementary groups are
typically just {0}. This is a correctness fix that brings the privilege drop
in line with POSIX practice, and matters more for any future reuse of this code.

Notes

  • <grp.h> is added for the setgroups() declaration; the cgo build uses
    -Werror=implicit-function-declaration, so a missing declaration would be
    fatal rather than silent.
  • The call fails closed: if setgroups() fails, set_eids() returns an error
    rather than continuing with a partial privilege drop.
  • setgroups(0, NULL) clears the list rather than installing the target user's
    own groups. Installing the real list would mean either an NSS lookup inside
    the cloned thread or widening au_ids_t to carry the group list from Go.
    Happy to do the latter if you'd prefer it — clearing seemed like the right
    minimal fix for the reported issue.

Testing

  • ./testSuccess, exit 0 (Fedora 44, Go 1.24, cgo, run as a non-root
    user)
  • ./build ignition — exit 0
  • git diff --check — clean
  • The probe above, compiled against the original and patched as_user.c

Running ./test as root instead fails TestTranslateTree/translate_7 in the
seven butane/base/* packages, because that test asserts a permission-denied
error which root bypasses. That failure reproduces identically on an unmodified
origin/main, so it is unrelated to this change.

No new in-tree test: internal/as_user is Linux-, cgo- and root-only, has no
existing unit tests, and a regression test for this would have to manipulate the
test process's own credentials. I'm glad to add a root-gated one if you want it.

Fixes #2242.

set_eids() switched the effective gid and uid to the target user but
never touched the supplementary group list, so the privilege-dropped
thread kept the caller's group memberships while acting as that user.
A directory reachable only through one of root's supplementary groups
stayed reachable for the duration of the switch.

Drop the list with setgroups(0, NULL) before relinquishing the gid and
uid, while the thread still holds the privilege required to make that
call, following the revocation order described in CERT POS36-C.

Fixes coreos#2242

Signed-off-by: Jason Colapietro <jasoncola1@gmail.com>
@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 09837c86-3c39-4c9a-8191-204869bbcacc

📥 Commits

Reviewing files that changed from the base of the PR and between f0dc24b and c6e73c9.

📒 Files selected for processing (1)
  • docs/release-notes.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/release-notes.md

Included review availability: Your plan provides up to 4 included reviews per hour; 2 remain after this review.


📝 Walkthrough

Walkthrough

set_eids now removes supplementary groups before lowering effective gid and uid privileges. It returns failure if group removal fails, updates its documentation, and adds a release-note entry.

Changes

Privilege-drop hardening

Layer / File(s) Summary
Clear supplementary groups during credential changes
internal/as_user/as_user.c, docs/release-notes.md
set_eids calls setgroups(0, NULL) before changing credentials and returns -1 if the call fails. Its documentation and the release notes describe the behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to c6e73

The change clears supplementary groups before relinquishing user and group privileges, preventing unintended retained access while acting as another user. It is localized and tested; no actionable merge-blocking risk remains.

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed The title follows the required format. It uses the subsystem prefix "internal/as_user", an imperative lowercase description, and no trailing period.
Description check ✅ Passed The description directly explains the supplementary-group issue, the fix, its impact, and validation results.
Linked Issues check ✅ Passed The changes satisfy issue [#2242] by clearing supplementary groups with setgroups(0, NULL) before changing the effective GID and UID, and by failing closed if the call fails.
Out of Scope Changes check ✅ Passed The code change, documentation update, and release-note entry are directly related to the supplementary-group privilege-drop fix described in [#2242].
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 1 files. (1 skipped: 1 …
Commit Message Convention ✅ Passed The PR contains one non-merge commit: internal/as_user: drop supplementary groups. The subsystem is a file-path prefix, the description starts with lowercase imperative drop, and it has no trailin…
Full details: Docstring Coverage

Explanation

Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 1 files. (1 skipped: 1 unsupported.)

Full details: Commit Message Convention

Explanation

The PR contains one non-merge commit: internal/as_user: drop supplementary groups. The subsystem is a file-path prefix, the description starts with lowercase imperative drop, and it has no trailing period. The merge commit is correctly excluded.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@JasonColapietro
JasonColapietro marked this pull request as ready for review August 22, 2026 01:30
@github-actions

Copy link
Copy Markdown

Binary size report (bin/amd64/ignition)

Size
Base (main) 33MiB
PR (#2301) 33MiB
Delta +16B (0.00%)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hardening: Supplementary groups are never dropped in privilege-drop (as_user.c)

1 participant